
import uuid

from blinker import Signal
from collections import Counter

from src.Core.Common import *
from src.Core.NodeBase import NodeBase
# 从 PyFlow 模块导入获取原始节点实例的函数
from src import getRawNodeInstance
# 从 PyFlow 模块导入根据引脚类型获取默认值的函数
from src import getPinDefaultValueByType
# 从 PyFlow 核心模块导入 Variable 类，用于表示变量
from src.Core.Variable import Variable
# 从 PyFlow 核心的接口模块导入 ISerializable 接口
from src.Core.Interfaces import ISerializable


class GraphBase(ISerializable):
    """
    表示节点图的数据结构。

    :var graphManager: 对图管理器的引用
    :vartype graphManager: :class:`~PyFlow.Core.GraphManager.GraphManager`

    :var nameChanged: 图名称更改后发出的信号
    :vartype nameChanged: :class:`~blinker.base.Signal`

    :var categoryChanged: 图类别更改后发出的信号
    :vartype categoryChanged: :class:`~blinker.base.Signal`

    :var childGraphs: 子图的集合
    :vartype childGraphs: :class:`set`

    :var _nodes: 节点存储。字典，键为 :class:`uuid.UUID`，值为 :class:`~PyFlow.Core.NodeBase.NodeBase`
    :vartype _nodes: :class:`dict`

    :var uid: 唯一标识符
    :vartype uid: :class:`uuid.UUID`

    .. py:method:: parentGraph
        :property:

        :getter: 返回对父图的引用，如果此图是根图则返回 None

        :setter: 将新图设置为此图的新父图

    .. py:method:: name
        :property:

        :getter: 返回图名称

        :setter: 设置新的图名称并触发信号

    .. py:method:: category
        :property:

        :getter: 返回图类别

        :setter: 设置新的图类别并触发信号

    .. py:method:: pins
        :property:

        :getter: 返回字典，键为 :class:`uuid.UUID`，值为 :class:`~PyFlow.Core.PinBase.PinBase`
        :rtype: dict

    """

    def __init__(
        self, name, manager, parentGraph=None, category="", uid=None, *args, **kwargs):
        """
        初始化图对象。

        :param name: 图的名称
        :param manager: 图管理器实例
        :param parentGraph: 父图对象，默认为 None
        :param category: 图的类别，默认为空字符串
        :param uid: 图的唯一标识符，默认为 None，若为 None 则自动生成
        :param args: 可变参数
        :param kwargs: 关键字参数
        """
        # 调用父类的构造函数
        super(GraphBase, self).__init__(*args, **kwargs)
        # 保存图管理器的引用
        self.graphManager = manager
        # 标记该图是否为根图，默认为 False
        self._isRoot = False

        # 定义图名称更改信号
        self.nameChanged = Signal(str)
        # 定义图类别更改信号
        self.categoryChanged = Signal(str)

        # 存储图的名称
        self.__name = name
        # 存储图的类别
        self.__category = category

        # 存储父图的引用
        self._parentGraph = None
        # 存储子图的集合
        self.childGraphs = set()

        # 设置父图
        self.parentGraph = parentGraph

        # 存储节点的字典，键为节点的唯一标识符，值为节点对象
        self._nodes = {}
        # 存储变量的字典，键为变量的唯一标识符，值为变量对象
        self._vars = {}
        # 生成图的唯一标识符，如果未提供则自动生成
        self.uid = uuid.uuid4() if uid is None else uid

        # 将当前图添加到图管理器中
        manager.add(self)

    def setIsRoot(self, bIsRoot):
        """
        将此图设置为根图。

        .. warning:: 内部使用

        :param bIsRoot: 是否为根图
        :type bIsRoot: :class:`bool`
        """
        self._isRoot = bIsRoot

    def isRoot(self):
        """
        判断此图是否为根图。

        :rtype: :class:`bool`
        """
        return self._isRoot

    def getVars(self):
        """
        返回此图的变量存储。

        :returns: 字典，键为 :class:`uuid.UUID`，值为 :class:`~PyFlow.Core.Variable.Variable`
        :rtype: :class:`dict`
        """
        return self._vars

    @property
    def parentGraph(self):
        """
        获取父图的引用。

        :return: 父图对象，如果此图是根图则返回 None
        """
        return self._parentGraph

    @parentGraph.setter
    def parentGraph(self, newParentGraph):
        """
        设置新的父图。

        :param newParentGraph: 新的父图对象
        """
        # 如果此图是根图，则不设置父图
        if self.isRoot():
            self._parentGraph = None
            return

        if newParentGraph is not None:
            if self._parentGraph is not None:
                # 从旧父图的子图集合中移除当前图
                if self in self._parentGraph.childGraphs:
                    self._parentGraph.childGraphs.remove(self)
            # 将当前图添加到新父图的子图集合中
            newParentGraph.childGraphs.add(self)
            # 更新父图引用
            self._parentGraph = newParentGraph

    def depth(self):
        """
        返回此图在图树中的深度级别。

        :rtype: int
        """
        result = 1
        parent = self._parentGraph
        # 遍历父图，计算深度
        while parent is not None:
            result += 1
            parent = parent.parentGraph
        return result

    def getVarList(self):
        """
        返回当前活动图及其所有父图的变量列表。

        :rtype: list(:class:`~PyFlow.Core.Variable.Variable`)
        """
        result = list(self._vars.values())
        parent = self._parentGraph
        # 遍历父图，收集所有变量
        while parent is not None:
            result += list(parent._vars.values())
            parent = parent.parentGraph
        return result

    def serialize(self, *args, **kwargs):
        """
        返回此图的序列化表示。

        :rtype: dict
        """
        result = {
            "name": self.name,  # 图的名称
            "category": self.category,  # 图的类别
            "vars": [v.serialize() for v in self._vars.values()],  # 序列化后的变量列表
            "nodes": [n.serialize() for n in self._nodes.values()],  # 序列化后的节点列表
            "depth": self.depth(),  # 图的深度
            "isRoot": self.isRoot(),  # 是否为根图
            "parentGraphName": str(self._parentGraph.name)
            if self._parentGraph is not None
            else str(None),  # 父图的名称，如果没有则为 None
        }
        return result

    def populateFromJson(self, jsonData):
        """
        根据序列化数据填充图。

        :param jsonData: 序列化的图数据
        :type jsonData: dict
        """
        # 清空当前图的内容
        self.clear()
        # 为图设置唯一的名称
        self.name = self.graphManager.getUniqGraphName(jsonData["name"])
        # 设置图的类别
        self.category = jsonData["category"]
        # 设置图是否为根图
        self.setIsRoot(jsonData["isRoot"])
        if self.isRoot():
            # 如果是根图，将名称设置为 "root"
            self.name = "root"
        # 恢复变量
        for varJson in jsonData["vars"]:
            var = Variable.deserialize(self, varJson)
            self._vars[var.uid] = var
        # 恢复节点
        for nodeJson in jsonData["nodes"]:
            # 检查节点是否为变量获取器或设置器，并传递变量
            nodeKwargs = {}
            if nodeJson["type"] in ("getVar", "setVar"):
                nodeKwargs["var"] = self._vars[uuid.UUID(nodeJson["varUid"])]
            nodeJson["owningGraphName"] = self.name
            node = getRawNodeInstance(
                nodeJson["type"],
                packageName=nodeJson["package"],
                libName=nodeJson["lib"],
                **nodeKwargs,
            )
            self.addNode(node, nodeJson)

        # 恢复连接
        for nodeJson in jsonData["nodes"]:
            for nodeOutputJson in nodeJson["outputs"]:
                for linkData in nodeOutputJson["linkedTo"]:
                    try:
                        # 根据唯一标识符获取左节点
                        lhsNode = self._nodes[uuid.UUID(linkData["lhsNodeUid"])]
                    except Exception as e:
                        # 如果根据唯一标识符获取失败，根据节点名称查找
                        lhsNode = self.findNode(linkData["lhsNodeName"])

                    try:
                        # 根据输出引脚 ID 获取左引脚
                        lhsPin = lhsNode.orderedOutputs[linkData["outPinId"]]
                    except Exception as e:
                        print("lhsPin not found {0}".format(str(linkData)))
                        continue

                    try:
                        # 根据唯一标识符获取右节点
                        rhsNode = self._nodes[uuid.UUID(linkData["rhsNodeUid"])]
                    except Exception as e:
                        # 如果根据唯一标识符获取失败，根据节点名称查找
                        rhsNode = self.findNode(linkData["rhsNodeName"])

                    try:
                        # 根据输入引脚 ID 获取右引脚
                        rhsPin = rhsNode.orderedInputs[linkData["inPinId"]]
                    except Exception as e:
                        continue

                    if not arePinsConnected(lhsPin, rhsPin):
                        # 尝试连接引脚
                        connected = connectPins(lhsPin, rhsPin)
                        # assert(connected is True), "Failed to restore connection"
                        if not connected:
                            print("Failed to restore connection", lhsPin, rhsPin)
                            connectPins(lhsPin, rhsPin)

    def remove(self):
        """
        移除当前图及其所有子图。最深层的图将首先被移除。
        """
        # 递归移除所有子图
        for childGraph in set(self.childGraphs):
            childGraph.remove()
        # 从图管理器中移除当前图
        self.graphManager.removeGraph(self)

    def clear(self):
        """
        清空当前图及其所有子图的内容。最深层的图将首先被清空。
        """
        # 递归清空所有子图
        for childGraph in set(self.childGraphs):
            childGraph.clear()

        # 清空当前图的节点
        for node in list(self._nodes.values()):
            node.kill()
        self._nodes.clear()

        # 清空当前图的变量
        for var in list(self._vars.values()):
            self.killVariable(var)
        self._vars.clear()

    @property
    def name(self):
        """
        获取图的名称。

        :return: 图的名称
        """
        return self.__name

    @name.setter
    def name(self, value):
        """
        设置图的名称，并在名称更改时发出信号。

        :param value: 新的图名称
        """
        value = str(value)
        if self.__name != value:
            self.__name = value
            # 发出名称更改信号
            self.nameChanged.send(self.__name)

    @property
    def category(self):
        """
        获取图的类别。

        :return: 图的类别
        """
        return self.__category

    @category.setter
    def category(self, value):
        """
        设置图的类别，并在类别更改时发出信号。

        :param value: 新的图类别
        """
        self.__category = str(value)
        # 发出类别更改信号
        self.categoryChanged.send(self.__category)

    def Tick(self, deltaTime):
        """
        定期执行，更新图中所有节点的状态。

        :param deltaTime: 自上次调用以来经过的时间
        :type deltaTime: float
        """
        for node in self._nodes.values():
            node.Tick(deltaTime)

    @property
    def pins(self):
        """
        获取图中所有节点的引脚。

        :return: 字典，键为引脚的唯一标识符，值为引脚对象
        :rtype: dict
        """
        result = {}
        for n in self.getNodesList():
            for pin in tuple(n.inputs.values()) + tuple(n.outputs.values()):
                result[pin.uid] = pin
        return result

    def createVariable(
        self,
        dataType=str("AnyPin"),
        accessLevel=AccessLevel.public,
        uid=None,
        name=str("var"),
    ):
        """
        在当前图的作用域内创建一个变量。

        :param dataType: 变量的数据类型，默认为 "AnyPin"
        :type dataType: str
        :param accessLevel: 变量的访问级别，默认为公共访问
        :type accessLevel: :class:`~PyFlow.Core.Common.AccessLevel`
        :param uid: 变量的唯一标识符，默认为 None，若为 None 则自动生成
        :type uid: :class:`uuid.UUID`
        :param name: 变量的名称，默认为 "var"
        :type name: str
        :return: 新创建的变量对象
        :rtype: :class:`~PyFlow.Core.Variable.Variable`
        """
        # 为变量生成唯一的名称
        name = self.graphManager.getUniqVariableName(name)
        var = Variable(
            self,
            getPinDefaultValueByType(dataType),
            name,
            dataType,
            accessLevel=accessLevel,
            uid=uid,
        )
        self._vars[var.uid] = var
        return var

    # TODO: add arguments to deal with references of this var
    # disconnect pins or mark nodes invalid
    def killVariable(self, var):
        """
        从当前图中移除一个变量。

        :param var: 要移除的变量对象
        :type var: :class:`~PyFlow.Core.Variable.Variable`
        """
        assert isinstance(var, Variable)
        if var.uid in self._vars:
            # 从变量存储中移除变量
            popped = self._vars.pop(var.uid)
            # 发出变量被移除的信号
            popped.killed.send()

    def getNodes(self):
        """
        返回此图的节点存储。

        :rtype: dict(:class:`~PyFlow.Core.NodeBase.NodeBase`)
        """
        return self._nodes

    def getNodesList(self, classNameFilters=None):
        """
        返回此图的节点列表。

        :param classNameFilters: 类名过滤器列表，若指定则只返回符合条件的节点，默认为 None
        :type classNameFilters: list(str)
        :rtype: list(:class:`~PyFlow.Core.NodeBase.NodeBase`)
        """
        if classNameFilters is None:
            classNameFilters = []
        if len(classNameFilters) > 0:
            return [
                n
                for n in self._nodes.values()
                if n.__class__.__name__ in classNameFilters
            ]
        else:
            return [n for n in self._nodes.values()]

    def findNode(self, name):
        """
        尝试根据节点名称查找节点。

        :param name: 节点名称
        :type name: str or None
        :return: 找到的节点对象，如果未找到则返回 None
        :rtype: :class:`~PyFlow.Core.NodeBase.NodeBase` or None
        """
        for i in self._nodes.values():
            if i.name == name:
                return i
        return None

    def getNodesByClassName(self, className):
        """
        返回按类名过滤的节点列表。

        :param className: 目标节点的类名
        :type className: str
        :rtype: list(:class:`~PyFlow.Core.NodeBase.NodeBase`)
        """
        nodes = []
        for i in self.getNodesList():
            if i.__class__.__name__ == className:
                nodes.append(i)
        return nodes

    def findPinByUid(self, uid):
        """
        尝试根据唯一标识符查找引脚。

        :param uid: 唯一标识符
        :type uid: :class:`~uuid.UUID`
        :rtype: :class:`~PyFlow.Core.PinBase.PinBase` or None
        """
        pin = None
        if uid in self.pins:
            pin = self.pins[uid]
        return pin

    def findPin(self, pinName):
        """
        尝试根据引脚名称查找引脚。

        :param pinName: 用于搜索的字符串
        :type pinName: str
        :rtype: :class:`~PyFlow.Core.PinBase.PinBase` or None
        """
        result = None
        for pin in self.pins.values():
            if pinName == pin.getFullName():
                result = pin
                break
        return result

    def getInputNode(self):
        """
        创建并向图中添加 :class:`~PyFlow.Packages.PyFlowBase.Nodes.graphNodes.graphInputs` 节点。

        此节点上的引脚将作为复合节点的输入引脚暴露。

        :rtype: :class:`~PyFlow.Core.NodeBase.NodeBase`
        """
        node = getRawNodeInstance("graphInputs", "PyFlowBase")
        self.addNode(node)
        return node

    def getOutputNode(self):
        """
        创建并向图中添加 :class:`~PyFlow.Packages.PyFlowBase.Nodes.graphNodes.graphOutputs` 节点。

        此节点上的引脚将作为复合节点的输出引脚暴露。

        :rtype: :class:`~PyFlow.Core.NodeBase.NodeBase`
        """
        node = getRawNodeInstance("graphOutputs", "PyFlowBase")
        self.addNode(node)
        return node

    def addNode(self, node, jsonTemplate=None):
        """
        将节点添加到存储中。

        :param node: 要添加的节点对象
        :type node: NodeBase
        :param jsonTemplate: 节点的序列化表示，当图反序列化时使用，用于在节点添加后执行自定义操作，默认为 None
        :type jsonTemplate: dict
        :rtype: bool
        """
        from src.Core.PathsRegistry import PathsRegistry

        assert node is not None, "failed to add node, None is passed"
        if node.uid in self._nodes:
            return False

        # 检查此节点是否为变量获取/设置节点。子图中创建的变量对父图不可见
        # 不破坏变量作用域
        if node.__class__.__name__ in ["getVar", "setVar"]:
            var = self.graphManager.findVariableByUid(node.variableUid())
            variableLocation = var.location()
            if len(variableLocation) > len(self.location()):
                return False
            if len(variableLocation) == len(self.location()):
                if Counter(variableLocation) != Counter(self.location()):
                    return False

        # 设置节点所属的图
        node.graph = weakref.ref(self)
        if jsonTemplate is not None:
            # 为节点生成唯一的名称
            jsonTemplate["name"] = self.graphManager.getUniqNodeName(
                jsonTemplate["name"]
            )
        else:
            # 为节点设置唯一的名称
            node.setName(self.graphManager.getUniqNodeName(node.name))

        # 将节点添加到节点存储中
        self._nodes[node.uid] = node
        # 节点创建后的处理
        node.postCreate(jsonTemplate)
        # 重建路径注册表
        PathsRegistry().rebuild()
        return True

    def location(self):
        """
        返回当前图在图树中的路径。

        示例:

        >>> ["root", "compound1", "compound2"]

        表示:

        >>> # root
        >>> # |- compound
        >>> #    |- compound2

        :rtype: list(str)
        """
        result = [self.name]
        parent = self._parentGraph
        while parent is not None:
            result.insert(0, parent.name)
            parent = parent.parentGraph
        return result

    def count(self):
        """
        返回图中节点的数量。

        :rtype: int
        """
        return self._nodes.__len__()

    def plot(self):
        """
        将图信息打印到控制台，可能对调试有用。
        """
        depth = self.depth()
        # 根据深度生成前缀，用于缩进显示
        prefix = "".join(["-"] * depth) if depth > 1 else ""
        parentGraphString = (
            str(None) if self.parentGraph is None else self.parentGraph.name
        )
        print(prefix + "GRAPH:" + self.name + ", parent:{0}".format(parentGraphString))

        assert self not in self.childGraphs

        for child in self.childGraphs:
            child.plot()